<template>
  <view id="content-box">
    <view :class="{'tabs-shadow': shadow && type!=='cart'}">
      <view :style="{height: tabHeight+'px', background: type==='default'?background:'', 'overflow': type==='default'?'hidden':'', 'padding': '0 ' + padding +'px', '--tab-width': tabWidth ? tabWidth+'px': 'auto', '--tab-height': tabHeight + 'px', '--font-size': fontSize + 'px', '--padding': padding + 'px', '--tab-padding': tabPadding + 'px', '--skew-deg': `${cartSkewDeg}deg`}">
        <view v-if="type==='cart'" class="tabs-cart" :class="{'tabs-cart-shadow': shadow}">
          <view v-for="(item,index) in tabList" :key="index" class="tab" :class="[index < tabCur ? 'little' : index === tabCur ? 'active' : 'large']" @click="tabChange" :data-cur="index">
            <view class="label">
              <view class="info">
                {{typeof item === 'string' ? item : item[nameProp]}}
                <view v-if="item[dotProp]" class="dot"></view>
                <view v-if="item[badgeProp]" class="badge">{{item[badgeProp]}}</view>
              </view>
            </view>
            <view v-if="index === tabCur" class="line" :style="{width: tabWidth? tabWidth/3+'px': '30px'}"></view>
          </view>
        </view>
        <view v-else class="tabs-default" :class="{'tabs-default-right': rightButton}">
          <scroll-view class="scrollBox" scroll-x scroll-with-animation :scroll-left="scrollLeft">
            <view class="tabs" :class="{'tabs-center': layout==='center','tabs-share': layout==='share', 'tabs-box': currentType==='box'}">
              <view v-for="(item,index) in tabList" :key="index" :id="'tab'+index" class="tab" @click="tabChange" :data-cur="index"
                    :class="{current: index===tabCur, bigger: index===tabCur && currentType === 'bigger', box: index===tabCur && currentType === 'box', last: index===tabList.length-1}">
                <view class="label" :class="{bubble: currentType === 'bubble', 'bubble-cur': currentType === 'bubble' && index===tabCur}" :style="{'--bubble-height': bubbleHeight+'px'}">
                  {{typeof item === 'string' ? item : item[nameProp]}}
                  <view v-if="item[dotProp]" class="dot"></view>
                  <view v-if="item[badgeProp]" class="badge">{{ item[badgeProp] }}</view>
                </view>
              </view>
              <view v-if="currentType === 'line'" class="tabs-line"
                    :style="{width: currentLineWidth+'px', height: lineHeight+'px', 'margin-left': lineLeft+'px', 'transition': lineTransition, backgroundColor: currentColor, bottom: (lineBottom+10)+'px'}"></view>
            </view>
          </scroll-view>
          <view v-if="rightButton" class="right-box" @click="clickRightButton">
            <xl-icon :name="rightIcon" :color="color" size="18"/>
          </view>
        </view>
      </view>
    </view>
    <!-- 显示切换 -->
    <view v-if="switchType==='show'">
      <!-- 注意：不能用index===tabCur三个等号，也不能用:style，vue的插槽也是个抽象元素，没有样式，只能用v-if -->
      <!-- <slot v-for="(item,index) in tabList" :name="'tabBox'+index" v-if="index==tabCur"/> -->
      <!-- 如果频繁切换，可以外加一层v-show控制，但也不能用block或者template这些抽象元素 -->
      <view v-for="(item,index) in tabList" :key="index" v-show="index===tabCur">
        <slot name="tabBox" :item="item" :index="index"></slot>
      </view>
    </view>
    <!-- 滑动切换 -->
    <swiper v-if="switchType==='swiper'" :current="tabCur" duration="300" :circular="true" @change="swiperChange"
            :style="scrollHeight">
      <swiper-item v-for="(item,index) in tabList" :key="index">
        <scroll-view class="contentScrollBox"
                     :scroll-top="scrollTop"
                     :scroll-y="canScroll"
                     @scroll="swiperScroll"
                     @scrolltolower="swiperScrolltolower"
                     :style="scrollHeight"
        >
          <slot name="tabBox" :item="item" :index="index"></slot>
        </scroll-view>
      </swiper-item>
    </swiper>
    <!-- 填充fixed位置 -->
    <!-- <view style="height: 84rpx;"></view> -->
  </view>
</template>

<script setup>
import {onPageScroll, onReachBottom} from "@dcloudio/uni-app";
import propsData from './props.js'

const props = defineProps(propsData)
import {ref, computed, onMounted, onBeforeUnmount, watch, useSlots, nextTick, getCurrentInstance} from 'vue'

const emits = defineEmits(['tabChange', 'load', 'scroll', 'reachBottom', 'clickRightButton'])

const scrollHeight = computed(() => {
  if (props.switchType === 'swiper') {
    if (Number(props.fixedHeight)) {
      let height = uni.$xl.parseUnit(props.fixedHeight, 'rpx');
      return `height: ${height};`
    } else {
      let height = uni.getWindowInfo().screenHeight - uni.getWindowInfo().statusBarHeight - uni.$xl.upx2px(0 + props.reduceHeight)
      return `height: ${height}px;-webkit-overflow-scrolling:touch;`
    }
  } else {
    return "";
  }
})

onMounted(() => {
  // 若tabList直传数组，则直接触发初始化
  if (props.tabList.length > 0) {
    // 一开始不要过渡效果，之后切换才有过渡效果
    nextTick(() => {
      changeTab(props.cur, false);
    })
  }
})
// 若tabList是动态请求，则监听变化触发初始化，如果直接监听props.tabList，小程序中直传数组，值一样也会触发监听，所以用JSON.stringify
watch(() => props.tabList, (newValue, oldValue) => {
  // 一开始不要过渡效果，之后切换才有过渡效果
  nextTick(() => {
    changeTab(props.cur, false);
  })
})


const tabCur = ref(props.cur)

// 点击切换Tab，如果用的是滑动切换，点击切换后这边改变了tabCur，会触发下面的swiperChange
function tabChange(e) {
  tabCur.value = e.currentTarget.dataset.cur;
  // 如果不是滑块，不会调用下面的切换，如果是滑块，会调用下面的切换
  if (props.switchType !== 'swiper') {
    changeTab(e.currentTarget.dataset.cur, true);
  }
}

// 滑动切换Tab，滑动切换不会触发上面的changeTab，所以把加载数据放这
function swiperChange(e) {
  let {current} = e.detail;
  changeTab(current, true);
}

// 提供给父组件进行ref来切换
defineExpose({changeTab, refreshTab})

// 切换tab处理方法，可以提供给父组件进行ref来切换
function changeTab(cur, trigger = false, transition = true) {
  // 设置当前指针
  tabCur.value = cur;
  // 切换需要过渡了
  if (props.type === 'default') updateLine(tabCur.value, transition);
  // 触发父组件切换操作
  if (trigger) emits("tabChange", tabCur.value);
  // 触发初始化操作
  initLoad();
  // 切换到滚动位置
  if (props.toScroll) toScrollTop();
}

// 刷新tab数据
function refreshTab(cur, needChange = false) {
  let refresh = cur || cur === 0 ? cur : tabCur.value
  isLoaded[refresh] = false
  if (needChange) {
    changeTab(cur, true);
  } else if (refresh === tabCur.value) {
    initLoad()
  }

  tabScrollTop[refresh] = 0;
}

// 判断是否已经第一次加载数据了
const isLoaded = []

// 初始化加载数据
function initLoad() {
  // 当前模块还没有加载
  if (!isLoaded[tabCur.value]) {
    isLoaded[tabCur.value] = true;
    // 触发父组件tabCur指定模块的初始化加载
    emits("load", tabCur.value);
  }
}

// scroll-view没有完全显示出来，不能向下滚动
const canScroll = ref(false)
// 创建一个交叉观察器，监控scroll-view是否完全显示
let observer = null;
// 滚动方式监控是否完全显示
// let scrollFullShow = false

const instance = getCurrentInstance();
// 记录每个模块的滚动位置
const tabScrollTop = []
// view显示切换时滚动事件，需要引入onPageScroll
if (props.switchType !== 'swiper') {
  // #ifndef APP-NVUE
  onPageScroll(e => {
    tabScrollTop[tabCur.value] = e.scrollTop;
  })
  // #endif
  // #ifdef APP-NVUE
  // 实时滚动条高度
  function windowScrollTop() {
    let documentScrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    tabScrollTop[tabCur.value] = documentScrollTop;
  }

  onMounted(() => {
    // 监听滚动条位置
    window.addEventListener('scroll', windowScrollTop, true)
  })
  onBeforeUnmount(() => {
    window.removeEventListener('scroll', () => {
    }); // 离开当前组件别忘记移除事件监听
  });
  // #endif
} else {
  if (props.needFullScroll) {
    // onPageScroll(e => {
    // 	uni.createSelectorQuery().in(instance).select('.contentScrollBox').boundingClientRect((data) => {
    // 		// bottom小于屏幕高时，说明完全显示了
    // 		if(data && data.bottom < uni.getWindowInfo().screenHeight + 10) scrollFullShow = true;
    // 		else scrollFullShow = false;
    // 	}).exec();
    // 	// 只要页面滚动，就让scroll-view可以滚动
    // 	canScroll.value = true
    // })
    // // #ifdef APP-NVUE
    // // 实时滚动条高度
    // function windowScrollTop() {
    // 	uni.createSelectorQuery().in(instance).select('.contentScrollBox').boundingClientRect((data) => {
    // 	  	// bottom小于屏幕高时，说明完全显示了
    // 	  	if(data && data.bottom < uni.getWindowInfo().screenHeight + 10) scrollFullShow = true;
    // 	  	else scrollFullShow = false;
    // 	}).exec();
    // 	// 只要页面滚动，就让scroll-view可以滚动
    // 	canScroll.value = true
    // }
    // onMounted(() => {
    // 	// 监听滚动条位置
    // 	window.addEventListener('scroll', windowScrollTop, true)
    // })
    // onBeforeUnmount(() => {
    // 	window.removeEventListener('scroll', () => {}); // 离开当前组件别忘记移除事件监听
    // });
    // // #endif

    onMounted(() => {
      observer = uni.createIntersectionObserver(instance, {
        thresholds: [0.9],
        observeAll: true, 			// 多个目标元素需要监听
        nativeMode: true			// 启用小程序原生的 IntersectionObserver。
      });
      observer.relativeToViewport().observe('.contentScrollBox', (res) => {
        if (res.intersectionRatio > 0.9) {
          // scroll-view 90%进入视窗
          canScroll.value = true;
        } else {
          // scroll-view 未完全进入视窗
          canScroll.value = false;
        }
      });
    });
    onBeforeUnmount(() => {
      if (observer) {
        observer.disconnect();
      }
    });
  } else {
    canScroll.value = true
  }
}

// 滑块切换时滚动事件，记录每个模块的滚动位置，并且可以用于父组件中顶部的吸顶
function swiperScroll(e) {
  // // 判断滚动方向
  // if (props.needFullScroll && e.detail.scrollTop > tabScrollTop[tabCur.value]) {
  // 	// 如果是向下滚动，并且scroll-view没有完全显示，则阻止滚动
  // 	if(!scrollFullShow) canScroll.value = false
  // }
  tabScrollTop[tabCur.value] = e.detail.scrollTop;
  emits("scroll", tabCur.value, e.detail.scrollTop);
}

const scrollTop = ref(-1)   // 使用固定高度面板时，使用scroll-view进行滚动
// 切换至记录的滚动位置
function toScrollTop() {
  if (tabScrollTop[tabCur.value]) {
    // 渲染完成后，跳至
    nextTick(() => {
      if (props.switchType === 'swiper') {
        scrollTop.value = tabScrollTop[tabCur.value]
      } else {
        uni.pageScrollTo({
          scrollTop: tabScrollTop[tabCur.value],
          duration: 0
        });
      }
    })
  } else {
    nextTick(() => {
      if (props.switchType === 'swiper') {
        scrollTop.value = 0
      } else {
        uni.pageScrollTo({
          scrollTop: 0,
          duration: 0
        });
      }
    })
  }
}


// 滑块切换时滑块触底事件
function swiperScrolltolower() {
  // 切换tab时，如果该tab已经到底了，切换过来，swiper会触发多次@scrolltolower，所以这边加上节流处理
  uni.$xl.throttle(() => {
    emits("reachBottom", tabCur.value);
  })
}

// 页面触底触发组件触底，需要在父组件中添加onPageScroll、onReachBottom方法，否则组件xl-tabs的onPageScroll、onReachBottom无效
if (props.switchType !== 'swiper') {
  onReachBottom(() => {
    emits("reachBottom", tabCur.value);
  })
}


// 得到元素的size
function getElementInfo(id) {
  return new Promise((res, rej) => {
    // 注意小程序中需要加上.in(this)才能获取
    // 选择器仅选取页面范围的节点，不会选取任何自定义组件中的节点，选择器的选取范围更改为自定义组件 component 内
    uni.createSelectorQuery().in(instance).select(id).fields({
      size: true,
      rect: true,
      scrollOffset: true
    }, (data) => {
      // {left: 170.5, right: 232.5, top: 0, bottom: 45, width: 62, …}
      // console.log(data)
      res(data);
    }).exec();
  });
}

let scrollLeft = ref(0) // 滚动偏移
let currentLineWidth = ref(50) // 线宽
let lineLeft = ref(0) // 线偏移
let lineTransition = ref('') // 过渡效果，一开始不需要
// 修改线样式
async function updateLine(current, transition = false) {
  let box = await getElementInfo('#content-box');
  // 获取选项的信息
  let info = await getElementInfo('#tab' + current);
  // 获取滑块的偏移信息
  let swiperInfo = await getElementInfo('.scrollBox');
  // 中间位置
  let middle = uni.getWindowInfo().screenWidth / 2
  if (info.left > middle) {
    // 如果右边的元素点击，要让点击元素居中，滚动条要滚到：原来滚动条偏移位置+元素左边到中间的位置+自身的一半
    scrollLeft.value = swiperInfo.scrollLeft + (info.left - middle) + info.width / 2;
  } else {
    // 如果左边的元素点击，要让点击元素居中，滚动条要滚到：原来滚动条偏移位置-元素左边到中间的位置+自身的一半
    scrollLeft.value = swiperInfo.scrollLeft - (middle - info.left) + info.width / 2;
  }

  // 当当前样式为下划线是变化
  if (props.currentType === 'line') {
    // 留白偏移
    let paddingLeft = props.padding;
    // 若有固定的当前下横线宽度，则使用固定宽度
    if (props.lineWidth) {
      currentLineWidth.value = props.lineWidth;
      if (current === 0) paddingLeft += props.tabPadding * 0.5
      lineLeft.value = info.left + swiperInfo.scrollLeft + (info.width - currentLineWidth.value) / 2 - box.left - paddingLeft;
    } else {
      currentLineWidth.value = info.width * 0.4;
      // 第一个左padding为0，所以还要加上左padding的0.4，props.tabPadding * 0.4
      if (current === 0) {
        currentLineWidth.value += props.tabPadding * 0.4;
        paddingLeft += props.tabPadding * 0.8
      }
      // 线的位置肯定是点击的时候，屏幕偏移加上滑动块偏移
      lineLeft.value = info.left + swiperInfo.scrollLeft + info.width * 0.3 - box.left - paddingLeft;
    }
    // 需不需要过渡
    if (transition) {
      lineTransition.value = 'width, margin-left 0.3s';
    } else {
      lineTransition.value = ''
    }
  }
}

// 点击右侧按钮操作
function clickRightButton() {
  emits("clickRightButton", tabCur.value);
}
</script>

<style lang="scss" scoped>
$font-size: var(--font-size);
$cart-color: v-bind(cartColor);
$cart-bg-color: v-bind(background);
$tab-width: var(--tab-width);
$tab-height: var(--tab-height);
$tab-padding: var(--tab-padding);
$angle-radius: 12px;
$line-height: calc($tab-height - $angle-radius); // 左右上边有圆角斜块
$skew-deg: var(--skew-deg);

@mixin tabSide($height, $bgColor, $skewX) {
  content: '';
  position: absolute;
  width: $angle-radius;
  height: $height;
  background: $bgColor;
  transform: skewX($skewX); // 倾斜
}


.content-box {
  width: 100%;
}

.tabs-shadow {
  box-shadow: 0 6rpx 16rpx 0 hsla(0, 0%, 60%, .1);
  margin-bottom: 16rpx;
}


.tabs-cart {
  background-color: $cart-bg-color;
  white-space: nowrap;
  /* #ifndef APP-PLUS-NVUE */
  display: flex;
  /* #endif */
  flex-direction: row !important;
  justify-content: space-around;
  overflow: hidden;
  border-radius: $angle-radius $angle-radius 0 0;

  &.tabs-cart-shadow {
    box-shadow: 0 -6rpx 8rpx rgba(0, 0, 0, 0.1);
    border-radius: $angle-radius $angle-radius 0 0;
  }

  .tab {
    position: relative;
    /* #ifndef APP-PLUS-NVUE */
    display: flex;
    /* #endif */
    flex-direction: row !important;
    justify-content: center;
    align-items: center;
    flex: 1;
    width: $tab-width;
    height: $tab-height;
    line-height: $tab-height;
    font-size: $font-size;
    padding: 0 $tab-padding;
    text-align: center;
    color: v-bind(color);
    font-size: 30rpx;
    border-radius: $angle-radius $angle-radius 0 0;

    .line {
      position: absolute;
      width: 30px;
      height: 3px;
      bottom: 6px;
      left: 51%;
      transform: translateX(-50%) skewX(-30deg) rotate(-5deg);
      background: linear-gradient(45deg, $cart-bg-color 40%, $cart-color);
    }

    .info {
      position: relative;

      .dot {
        position: absolute;
        right: -10px;
        top: calc($tab-height / 2 - 8px);
        width: 7px;
        height: 7px;
        border-radius: 50%;
        background: #f56c6c;
        z-index: 9;
      }

      .badge {
        position: absolute;
        right: -14px;
        top: calc($tab-height / 2 - 11px);
        width: 12px;
        height: 12px;
        line-height: 12px;
        border-radius: 50%;
        background: #f56c6c;
        color: #ffffff;
        font-size: 20rpx;
        z-index: 9;
      }
    }

    &.active {
      background: $cart-color;
      box-shadow: 24px $line-height 0 $cart-color, -24px $line-height 0 0 $cart-color; // 多出底部
      z-index: 99;
      color: v-bind(currentColor);

      &::before {
        @include tabSide($line-height, $cart-color, calc($skew-deg * -1));
        top: 0;
        left: calc($angle-radius * -0.5);
        border-top-left-radius: $angle-radius;
        box-shadow: -4px -2px 4px rgba(100, 100, 100, 0.1)
      }

      &::after {
        @include tabSide($line-height, $cart-color, $skew-deg);
        top: 0;
        right: calc($angle-radius * -0.5);
        border-top-right-radius: $angle-radius;
        box-shadow: 4px -2px 4px rgba(100, 100, 100, 0.1)
      }

      .label {
        &::before {
          @include tabSide(calc($angle-radius + 3px), $cart-bg-color, calc($skew-deg * -1));
          bottom: 0;
          left: calc($angle-radius * -2 - 1px);
          border-bottom-right-radius: $angle-radius;
          box-shadow: inset -3px -1px 2px rgba(100, 100, 100, .1)
        }

        &::after {
          @include tabSide(calc($angle-radius + 3px), $cart-bg-color, $skew-deg);
          bottom: 0;
          right: calc($angle-radius * -2 - 1px);
          border-bottom-left-radius: $angle-radius;
          box-shadow: inset 3px -1px 2px rgba(100, 100, 100, .1)
        }
      }
    }

    &.little {
      &::before {
        @include tabSide($line-height, $cart-bg-color, calc($skew-deg * -1));
        top: 0;
        left: calc($angle-radius * -0.5);
        border-top-left-radius: $angle-radius;
        box-shadow: -4px -2px 4px rgba(100, 100, 100, 0.1)
      }

      .label {
        &::before {
          @include tabSide(calc($angle-radius + 3px), $cart-bg-color, calc($skew-deg * -1));
          bottom: 0;
          left: calc($angle-radius * -2 - 1px);
          border-bottom-right-radius: $angle-radius;
          box-shadow: inset -3px -1px 2px rgba(100, 100, 100, .1)
        }
      }
    }

    &.large {
      &::after {
        @include tabSide($line-height, $cart-bg-color, $skew-deg);
        top: 0;
        right: calc($angle-radius * -0.5);
        border-top-right-radius: $angle-radius;
        box-shadow: 4px -2px 4px rgba(100, 100, 100, 0.1);
      }

      .label {
        &::after {
          @include tabSide(calc($angle-radius + 3px), $cart-bg-color, $skew-deg);
          bottom: 0;
          right: calc($angle-radius * -2 - 1px);
          border-bottom-left-radius: $angle-radius;
          box-shadow: inset 3px -1px 2px rgba(100, 100, 100, .1);
        }
      }
    }
  }
}

.tabs-default {
  /* #ifndef APP-PLUS-NVUE */
  display: flex;
  /* #endif */
  flex-direction: row !important;

  &.tabs-default-right {
    .scrollBox {
      width: calc(700rpx - var(--padding) * 2);
    }

    .right-box {
      width: 40rpx;
      height: $tab-height;
      line-height: $tab-height;
      padding-left: 10rpx;
      /* #ifndef APP-PLUS-NVUE */
      display: flex;
      /* #endif */
      flex-direction: row !important;
      justify-content: center;
      align-items: center;
    }
  }

  .tabs {
    white-space: nowrap;
    position: relative;

    .tab {
      display: inline-block;
      width: $tab-width;
      height: calc($tab-height + 7px);
      line-height: $tab-height;
      font-size: $font-size;
      padding: 0 $tab-padding;
      color: v-bind(color);
      text-align: center;
      transition: transform 0.3s;

      &:first-child {
        padding-left: 0 !important;
      }

      &.last {
        padding-right: 0 !important;
      }

      &.current {
        font-weight: bold;
        color: v-bind(currentColor);
      }

      /* 当前放大样式 */
      &.bigger {
        transform: scale(1.2);
      }

      /* 当前box样式 */
      &.box {
        background: v-bind(boxColor);
      }

      /* 当前气泡样式 */
      .bubble {
        padding: 0 20rpx;
        position: relative;

        &.label {
          .dot {
            right: 0px;
          }

          .badge {
            right: -4px;
          }
        }
      }

      .bubble-cur {
        height: var(--bubble-height);
        line-height: calc(var(--bubble-height) + 2px);
        margin-top: calc(($tab-height - var(--bubble-height)) / 2);
        border-radius: calc(var(--bubble-height) / 2);
        background: v-bind(bubbleColor);

        &::after {
          content: " ";
          position: absolute;
          height: 0;
          width: 0;
          border: solid transparent;
          top: 100%;
          left: 50%;
          border-top-color: v-bind(bubbleColor);
          transform: translateX(-50%);
          border-width: 4px;
        }

        &.label {
          .dot {
            top: 0px;
          }

          .badge {
            top: -3px;
          }
        }
      }

      .label {
        position: relative;

        .dot {
          position: absolute;
          right: -10px;
          top: calc($tab-height / 2 - 8px);
          width: 7px;
          height: 7px;
          border-radius: 50%;
          background: #f56c6c;
        }

        .badge {
          position: absolute;
          right: -14px;
          top: calc($tab-height / 2 - 11px);
          width: 12px;
          height: 12px;
          line-height: 13px;
          border-radius: 50%;
          background: #f56c6c;
          color: #ffffff;
          font-size: 18rpx;
          text-align: center;
        }
      }
    }

    /* 当前下划线样式 */
    .tabs-line {
      position: absolute;
      bottom: 2px;
      left: 0;
      z-index: 1;
      width: 94rpx;
      height: 6rpx;
      background-color: #333;
      border-radius: 6px;
      margin-left: 0px;
    }

    &.tabs-center {
      /* #ifndef APP-PLUS-NVUE */
      display: flex;
      /* #endif */
      flex-direction: row !important;
      justify-content: center;
    }

    &.tabs-share {
      /* #ifndef APP-PLUS-NVUE */
      display: flex;
      /* #endif */
      flex-direction: row !important;

      .tab {
        flex: 1;
      }
    }

    &.tabs-box .tab:first-child {
      padding-left: $tab-padding !important;
    }

    &.tabs-box .tab.last {
      padding-right: $tab-padding !important;
    }
  }
}
</style>
